package org.budo.mybatis;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.sql.DataSource;

import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.budo.mybatis.dao.MybatisDao;
import org.budo.mybatis.dao.impl.MybatisDaoImpl;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.spring.bean.factory.util.FactoryBeanUtil;
import org.budo.support.spring.util.SpringUtil;
import org.mybatis.spring.SqlSessionFactoryBean;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class Mybatis {
    public static Integer countBySql(DataSource dataSource, String sql, Map<String, Object> parameters) {
        MybatisDao mybatisDao = dao(dataSource);
        return mybatisDao.findBySql(Integer.class, sql, parameters);
    }

    public static <T> T findBySql(DataSource dataSource, Class<T> entityType, String sql, Map<String, Object> parameters) {
        MybatisDao mybatisDao = dao(dataSource);
        return mybatisDao.findBySql(entityType, sql, parameters);
    }

    public static MybatisDao dao(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        SqlSessionFactory sqlSessionFactory = FactoryBeanUtil.getObject(sqlSessionFactoryBean);

        MybatisDaoImpl mybatisDao = new MybatisDaoImpl();
        mybatisDao.setSqlSessionFactory(sqlSessionFactory);

        return mybatisDao;
    }

    public static String fieldNameToColumn(Class<?> entityType, String fieldName) {
        SqlSessionFactory sqlSessionFactory = SpringUtil.getBean(SqlSessionFactory.class);

        String fieldNameToColumn_1 = fieldNameToColumn_1(sqlSessionFactory, entityType, fieldName);
        if (null != fieldNameToColumn_1 && !fieldNameToColumn_1.trim().isEmpty()) {
            return fieldNameToColumn_1;
        }

        MybatisDao mybatisDao = SpringUtil.getBean(MybatisDao.class);
        if (null != mybatisDao && mybatisDao instanceof MybatisDaoImpl) {
            SqlSessionFactory sqlSessionFactory_2 = ((MybatisDaoImpl) mybatisDao).getSqlSessionFactory();
            String fieldNameToColumn_2 = fieldNameToColumn_1(sqlSessionFactory_2, entityType, fieldName);
            if (null != fieldNameToColumn_2 && !fieldNameToColumn_2.trim().isEmpty()) {
                return fieldNameToColumn_2;
            }
        }

        List<Field> allFields = ReflectUtil.getAllFields(entityType);
        for (Field field : allFields) {
            if (fieldName.equals(field.getName())) {
                Column column = field.getAnnotation(Column.class);
                if (null != column && null != column.name() && !column.name().trim().isEmpty()) {
                    return column.name();
                }
            }
        }

        return fieldName;
    }

    /**
     * fieldNameToColumn_1
     */
    private synchronized static String fieldNameToColumn_1(SqlSessionFactory sqlSessionFactory, Class<?> entityType, String fieldName) {
        if (null == sqlSessionFactory) {
            log.warn("#48 fieldNameToColumn_1, sqlSessionFactory = " + sqlSessionFactory + ", entityType=" + entityType + ", fieldName=" + fieldName);
            return null;
        }

        Configuration configuration = sqlSessionFactory.getConfiguration();
        Collection<ResultMap> resultMaps = configuration.getResultMaps();

        for (Object each : resultMaps) { // ConcurrentModificationException: null
            if (null == each || !(each instanceof ResultMap)) {
                continue;
            }

            ResultMap resultMap = (ResultMap) each;

            if (!resultMap.getType().equals(entityType)) {
                continue;
            }

            for (ResultMapping resultMapping : resultMap.getResultMappings()) {
                if (resultMapping.getProperty().equals(fieldName.replace("`", ""))) {
                    return resultMapping.getColumn();
                }
            }
        }

        return null;
    }
}